﻿// -----------------------------------------------------------------------
// <copyright file="OptionalGenerator.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Generators
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq.Expressions;
    using System.Reflection;
    using FluentSerializer.Utilities;

    /// <summary>
    /// 
    /// </summary>
    public static class OptionalGenerator
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="serializer"></param>
        /// <param name="parametersType"></param>
        /// <param name="type"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "More readable this way")]
        public static Descriptor BuildSerializer(Serializer serializer, Type parametersType, Type type, Type helper)
        {
            bool isUniversal = true;

            Type decoratedType = type;
            Type realType = Serializer.UndecorateType(type);

            if (realType.IsValueType)
            {
                realType = typeof(Nullable<>).MakeGenericType(realType);
            }

            Type decoratedSubType = type.GetGenericArguments()[0];
            Type realSubType = Serializer.UndecorateType(decoratedSubType);

            bool isNullable = realSubType.IsValueType;

            Descriptor descriptor1 = Serializer.GetSerializer(serializer, typeof(bool), typeof(bool), helper);
            Descriptor descriptor2 = Serializer.GetSerializer(serializer, realSubType, decoratedSubType, helper);

            isUniversal &= descriptor1.IsUniversal;
            isUniversal &= descriptor2.IsUniversal;

            ParameterExpression item = Expression.Parameter(realType);
            ParameterExpression parameters = parametersType != null ? Expression.Parameter(parametersType) : null;
            ParameterExpression subItem = Expression.Variable(realSubType);

            ParameterExpression hasValue = Expression.Variable(typeof(bool));
            Expression getHasValue = isNullable ? (Expression)Expression.Property(item, "HasValue") : Expression.ReferenceNotEqual(item, Expression.Constant(null));
            BinaryExpression assignHasValue = Expression.Assign(hasValue, getHasValue);
            Expression value = isNullable ? (Expression)Expression.Property(item, "Value") : item;

            LambdaExpression disposeExpression;
            LambdaExpression deserializeExpression;
            LambdaExpression serializeExpression;
            LambdaExpression sizeExpression;

            /***/
            {
                if (descriptor2.DeserializationHandling.HasFlag(DeserializationHandling.RequireDispose))
                {
                    disposeExpression = Serializer.MakeLambda(Expression.IfThen(hasValue, Serializer.MakeFromLambda(descriptor2.DisposeExpression, false, value)), false, item);
                }
                else
                {
                    disposeExpression = null;
                }
            }

            /***/
            {
                Expression deserializeHasValue = Serializer.MakeFromLambda(descriptor1.DeserializeExpression, descriptor1.IsUniversal, Serializer.ReaderExpression, hasValue, parameters);

                Expression ifTrue, ifFalse;

                if (isNullable)
                {
                    ConstructorInfo constructor = realType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new[] { realSubType }, null);

                    ifTrue = Expression.New(
                        constructor,
                        Serializer.MakeFromLambda(
                            descriptor2.DeserializeExpression,
                            descriptor2.IsUniversal,
                            Serializer.ReaderExpression,
                            subItem,
                            parameters));
                }
                else
                {
                    ifTrue = Serializer.MakeFromLambda(
                        descriptor2.DeserializeExpression,
                        descriptor2.IsUniversal,
                        Serializer.ReaderExpression,
                        subItem,
                        parameters);
                }

                if (IsExpressionReferenced.Check(ifTrue, subItem) != 0)
                {
                    ifTrue = Expression.Block(
                        new[] { subItem },
                        new Expression[] 
                        {
                            Expression.Assign(subItem, Expression.Condition(hasValue, value, descriptor2.DefaultExpression)),
                            ifTrue
                        });
                }

                ifFalse = Expression.Default(realType);

                if (disposeExpression != null)
                {
                    ifFalse = Expression.Block(Expression.IfThen(hasValue, Serializer.MakeFromLambda(descriptor2.DisposeExpression, false, value)), ifFalse);
                }

                Expression check = Expression.Condition(deserializeHasValue, ifTrue, ifFalse, realType);

                if (IsExpressionReferenced.Check(check, hasValue) != 0)
                {
                    check = Expression.Block(new[] { hasValue }, new Expression[] { assignHasValue, check });
                }

                deserializeExpression = Serializer.MakeLambda(check, isUniversal, Serializer.ReaderExpression, item, parameters);
            }

            /***/
            {
                Expression serializeHasValue = Serializer.MakeFromLambda(descriptor1.SerializeExpression, descriptor1.IsUniversal, Serializer.WriterExpression, hasValue, parameters);

                ConditionalExpression check = Expression.IfThen(
                    hasValue,
                    Serializer.MakeFromLambda(descriptor2.SerializeExpression, descriptor2.IsUniversal, Serializer.WriterExpression, value, parameters));

                serializeExpression = Serializer.MakeLambda(Expression.Block(new[] { hasValue }, new Expression[] { assignHasValue, serializeHasValue, check }), isUniversal, Serializer.WriterExpression, item, parameters);
            }

            /***/
            {
                Expression sizeBool = Serializer.MakeFromLambda(descriptor1.SizeExpression, descriptor1.IsUniversal, hasValue, parameters);

                ConditionalExpression check = Expression.Condition(
                    hasValue,
                    Expression.Add(
                        sizeBool,
                        Serializer.MakeFromLambda(descriptor2.SizeExpression, descriptor2.IsUniversal, value, parameters)),
                    sizeBool,
                    typeof(int));

                sizeExpression = Serializer.MakeLambda(Expression.Block(new[] { hasValue }, new Expression[] { assignHasValue, check }), isUniversal, item, parameters);
            }

            var descriptor = new Descriptor(type, isUniversal, deserializeExpression, DeserializationHandling.RequireAssignment, serializeExpression, sizeExpression, disposeExpression);
            return descriptor;
        }
    }
}